/*
 * Copyright (C) 2014 Wei Chou (weichou2010@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wei.c.anno;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import android.app.Activity;
import android.app.Dialog;
import android.util.SparseArray;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;

/**
 * @author 周伟 Wei Chou(weichou2010@gmail.com)
 */
public class Injector {
	public static void inject(Object injectObj, Activity activity) {
		inject(injectObj, activity, injectObj.getClass());
	}

	public static void inject(Object injectObj, Activity activity, Class<?> stopSearch) {
		try {
			injectView(injectObj, activity, stopSearch);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static void inject(Object injectObj, Dialog dialog) {
		inject(injectObj, dialog, injectObj.getClass());
	}

	public static void inject(Object injectObj, Dialog dialog, Class<?> stopSearch) {
		try {
			injectView(injectObj, dialog, stopSearch);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static void inject(View view) {
		inject(view, view.getClass());
	}

    public static void inject(View view, Class<?> stopSearch) {
        inject(view, view, stopSearch);
    }

	public static void inject(Object injectObj, View view, Class<?> stopSearch) {
		try {
			injectView(injectObj, view, stopSearch);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static int layoutID(Class<?> clazz) {
		ViewLayoutId layoutId = clazz.getAnnotation(ViewLayoutId.class);
		return layoutId != null ? layoutId.value() : 0;
	}

	public static int listViewID(Class<?> clazz) {
		ViewListId listId = clazz.getAnnotation(ViewListId.class);
		return listId != null ? listId.value() : 0;
	}

	private static void injectView(final Object injectObj, final Object contaner, final Class<?> stopSearch) throws Exception {
		//inject @ViewLayoutId
	    int layoutId = layoutID(injectObj.getClass());
	    if (layoutId > 0) {
	        if (contaner instanceof Activity) {
	            ((Activity)contaner).setContentView(layoutId);
	        } else if (contaner instanceof Dialog) {
	            ((Dialog)contaner).setContentView(layoutId);
	        } else if (injectObj instanceof ViewGroup && contaner == injectObj) {
	            ViewGroup viewGroup = (ViewGroup)contaner;
	            View.inflate(viewGroup.getContext(), layoutId, viewGroup);
	        }
	    }
		final SparseArray<View> id2Views = new SparseArray<View>();
		final Map<OnClickListener, Ids> ocl2Ids = new HashMap<OnClickListener, Ids>();
		final List<Field> fields = ReflectUtils.getFields(injectObj.getClass(), stopSearch);
		ViewId viewId;
		View view;
		Class<?> type;
		ViewOnClick viewOnClick;
		for (Field field : fields) {
			type = field.getType();
			if (View.class.isAssignableFrom(type)) {
				//inject @ViewId
				viewId = field.getAnnotation(ViewId.class);
				if (viewId != null) {
					view = findViewById(contaner, viewId.value());
					if (view != null) {
						field.setAccessible(true);
						field.set(injectObj, view);
                        field.setAccessible(false);
						//inject @ViewOnClick
						if (field.getAnnotation(ViewOnClick.class) != null) {
							view.setOnClickListener((OnClickListener)injectObj);
						}
						id2Views.put(viewId.value(), view);
					}
				}
			} else if (OnClickListener.class.isAssignableFrom(type)) {
				//inject @ViewOnClick
				viewOnClick = field.getAnnotation(ViewOnClick.class);
				if (viewOnClick != null) {
					field.setAccessible(true);
					OnClickListener onClickListener = (OnClickListener)field.get(injectObj);
					field.setAccessible(false);
					if (onClickListener != null) {
						ocl2Ids.put(onClickListener, viewOnClick.value());
					}
				}
			}
		}
		//inject @ViewOnClick
		final List<Method> methods = ReflectUtils.getMethods(injectObj.getClass(), stopSearch);
        //final Method onClickMethod = ReflectUtils.getMethod(OnClickListener.class, "onClick", View.class); //不兼容混淆
        final Method onClickMethod = OnClickListener.class.getDeclaredMethods()[0];
        final String onClickMethodName = onClickMethod.getName();
        final Class<?> onClickMethodReturnType = onClickMethod.getReturnType();
        final Class<?>[] onClickMethodParameterTypes = onClickMethod.getParameterTypes();
		for (final Method method : methods) {
			viewOnClick = method.getAnnotation(ViewOnClick.class);
			if (viewOnClick != null) {
				for (int id : viewOnClick.value().value()) {
					view = id2Views.get(id);
					if (view == null) {
						view = findViewById(contaner, id);
					}
					if (view != null) {
					    if (injectObj instanceof OnClickListener
                                && method.getName().equals(onClickMethodName)
                                && method.getReturnType().equals(onClickMethodReturnType)
                                && Arrays.equals(method.getParameterTypes(), onClickMethodParameterTypes)) {
							view.setOnClickListener((OnClickListener)injectObj);
						} else if (method.getParameterTypes().length <= 0) {
							view.setOnClickListener(new OnClickListener() {
								@Override
								public void onClick(View v) {
									try {
										method.setAccessible(true);
										method.invoke(injectObj);
										method.setAccessible(false);
									} catch (Exception e) {
										throw new RuntimeException(e);
									}
								}
							});
						} else {
							view.setOnClickListener(new OnClickListener() {
								@Override
								public void onClick(View v) {
									try {
										method.setAccessible(true);
										method.invoke(injectObj, v);
										method.setAccessible(false);
									} catch (Exception e) {
										throw new RuntimeException(e);
									}
								}
							});
						}
					}
				}
			}
		}
		//inject @ViewOnClick. 优先级最高，会覆盖掉前面的
		final Set<Map.Entry<OnClickListener, Ids>> entries = ocl2Ids.entrySet();
		for (Map.Entry<OnClickListener, Ids> entry : entries) {
			for (int id : entry.getValue().value()) {
				view = id2Views.get(id);
				if (view == null) {
					view = findViewById(contaner, id);
				}
				if (view != null) {
					view.setOnClickListener(entry.getKey());
				}
			}
		}
	}

	private static View findViewById(final Object contaner, final int id) {
		View view;
		if (contaner instanceof Activity) {
			view = ((Activity)contaner).findViewById(id);
		} else if (contaner instanceof Dialog) {
			view = ((Dialog)contaner).findViewById(id);
		} else if (contaner instanceof View) {
			view = ((View)contaner).findViewById(id);
		} else {
			view = null;
		}
		return view;
	}
}
